package applicationServer.connection;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.EventObject;
import java.util.Iterator;
import java.util.Properties;
import java.util.Random;
import java.util.TooManyListenersException;
import java.util.Vector;

import javax.sdp.Attribute;
import javax.sdp.Connection;
import javax.sdp.MediaDescription;
import javax.sdp.SdpException;
import javax.sdp.SdpFactory;
import javax.sdp.SessionDescription;
import javax.sip.ClientTransaction;
import javax.sip.Dialog;
import javax.sip.DialogTerminatedEvent;
import javax.sip.IOExceptionEvent;
import javax.sip.InvalidArgumentException;
import javax.sip.ListeningPoint;
import javax.sip.PeerUnavailableException;
import javax.sip.RequestEvent;
import javax.sip.ResponseEvent;
import javax.sip.ServerTransaction;
import javax.sip.SipException;
import javax.sip.SipFactory;
import javax.sip.SipListener;
import javax.sip.SipProvider;
import javax.sip.SipStack;
import javax.sip.TimeoutEvent;
import javax.sip.TransactionTerminatedEvent;
import javax.sip.TransactionUnavailableException;
import javax.sip.address.Address;
import javax.sip.address.AddressFactory;
import javax.sip.address.SipURI;
import javax.sip.header.CSeqHeader;
import javax.sip.header.CallIdHeader;
import javax.sip.header.ContactHeader;
import javax.sip.header.ContentTypeHeader;
import javax.sip.header.FromHeader;
import javax.sip.header.Header;
import javax.sip.header.HeaderFactory;
import javax.sip.header.MaxForwardsHeader;
import javax.sip.header.ToHeader;
import javax.sip.header.ViaHeader;
import javax.sip.message.MessageFactory;
import javax.sip.message.Request;
import javax.sip.message.Response;

import applicationServer.configurationManagement.Configuration;
import applicationServer.logic.ClientManager;
import applicationServer.logic.StreamerInfo;

public class ConnectionManager implements SipListener{

	private Configuration _config = null;
	ClientManager _clientManager = null;

	private SipStack sipStack;
	private SipFactory sipFactory;
	private SdpFactory sdpFactory;
	private AddressFactory addressFactory;
	private HeaderFactory headerFactory;
	private MessageFactory messageFactory;
	private SipProvider sipProvider;
	private ListeningPoint udp;
	private Random random = new Random();
	private ListeningPoint listeningPoint;
	private String transport;
	
	//private Hashtable<String, Vector> responseContext;
	//private Hashtable <String, SipURI> sessionURIs;
	//private Hashtable<SipURI,SessionStatus> tableOfSessions;

	
	public ConnectionManager (Configuration config, ClientManager clientm) {
		
		_config = config;
		_clientManager = clientm;

		transport = ListeningPoint.UDP;
		// TODO Auto-generated constructor stub
		//Start SIP
		sipFactory = SipFactory.getInstance();
		Properties stackProp = new Properties();
		sipFactory.setPathName("gov.nist");
		stackProp.put("javax.sip.STACK_NAME" ,  _config.get_Name() + "SipStack");
		try {
			// Create SipStack object
			sipStack = (SipStack)sipFactory.createSipStack(stackProp);
		} catch(PeerUnavailableException e) {
			System.err.println(e.getMessage());
			System.exit(-1);
		}
		// Set name of SipStack
		//sipStack.setStackName("Reference Implementation SIP stack");
		try {
			// Get SipStack's ListeningPoints
			listeningPoint = sipStack.createListeningPoint(_config.get_address(),_config.get_port(), ListeningPoint.UDP);
			// Create SipProvider based on the first ListeningPoint
			sipProvider = sipStack.createSipProvider(listeningPoint);
		} catch(InvalidArgumentException e){
			System.err.println("Stack has no ListeningPoints");
			System.exit(-1);
		} catch(NullPointerException e) {
			System.err.println("Stack has no ListeningPoints");
			System.exit(-1);
		} catch(SipException e) {
			System.err.println(e.getMessage());
			System.exit(-1);
		}
		// Register this application as a SipListener of the SipProvider
		try {
			sipProvider.addSipListener(this);
		} catch(TooManyListenersException e) {
			// A limit has been reached on the number of Listeners allowed per provider
			System.err.println(e.getMessage());
			System.exit(-1);
		}
		try {
			// Create AddressFactory
			addressFactory = sipFactory.createAddressFactory();
			// Create HeaderFactory
			headerFactory = sipFactory.createHeaderFactory();
			// Create MessageFactory
			messageFactory = sipFactory.createMessageFactory();
		} catch(PeerUnavailableException e) {
			System.err.println(e.getMessage());
			System.exit(-1);
		}
		
	}

	public void sendRequest(StreamerInfo si, String media, MediaDescription md) {


		Request invite = null;
		/*Request options = null;
		Request register = null;*/
		sdpFactory = SdpFactory.getInstance();

		random = new Random();
		int tagInitial = random.nextInt();
		//int tagResponse = random.nextInt();
		int tagVIA = random.nextInt();
		long initial = 1;
		
		SipURI fromAddress = null;
		FromHeader fromHeader = null;
		Address fromNameAddress = null;

		SipURI toAddress = null;
		Address toNameAddress = null;
		ToHeader toHeader = null;
		
		SipURI requestURI = null;
		CallIdHeader callIdHeader = null;
		CSeqHeader cSeqHeader = null;
		ViaHeader viaHeader = null;
		MaxForwardsHeader maxForwardsHeader = null;
		//SDP
		SessionDescription sdp = null;
		ContactHeader contact = null;
		
		ArrayList<ViaHeader> viaHeaders = null;
		ContentTypeHeader contentTypeHeader = null;
		try {
			// create From Header
			
			fromAddress = addressFactory.createSipURI(_config.get_Name(), _config.get_address() + ":" + _config.get_port() );
			fromAddress.setHost(_config.get_address());
			fromAddress.setPort(_config.get_port());
			fromNameAddress = addressFactory.createAddress(_config.get_Name() + "@" + _config.get_address() + ":" + _config.get_port());
			fromHeader = headerFactory.createFromHeader(fromNameAddress, Integer.toHexString(tagInitial));
			
			toAddress = addressFactory.createSipURI(si.get_name(),si.get_address() + ":" + si.get_port());
			toAddress.setHost(si.get_address());
			toAddress.setPort(si.get_port());
			toNameAddress = addressFactory.createAddress(si.get_name() + "@" + si.get_address() + ":" + si.get_port());
			toHeader = headerFactory.createToHeader(toNameAddress, null);
			
			// create Request URI
			requestURI = (SipURI)toAddress.clone();
			requestURI.setTransportParam(sipProvider.getListeningPoint(ListeningPoint.UDP).getTransport());
			// Create ViaHeaders
			String transport = ListeningPoint.UDP;
			
			viaHeader = headerFactory.createViaHeader(_config.get_address(), _config.get_port(), transport,"z9hG4bK" + Integer.toString(tagVIA));
			viaHeaders = new ArrayList<ViaHeader>();
			viaHeaders.add(viaHeader);
			contact = headerFactory.createContactHeader(fromNameAddress);
			// Create ContentTypeHeader
			contentTypeHeader = headerFactory.createContentTypeHeader("application", "sdp");
			
			int[] staticRtpAvpTypes = new int[1];
			staticRtpAvpTypes[0] = 98;
			
			sdp = sdpFactory.createSessionDescription();
			/*md = sdpFactory.createMediaDescription("video", _config.get_receivingport(), 1, "RTP/AVP", staticRtpAvpTypes);
			md.setConnection(c);
			md.setAttribute("bc_service", media );
			//md.setAttribute("rtpmap", "98 " + _config.get_codec());
			md.setAttribute("recvonly", "");*/
			sdp.getMediaDescriptions(true).add(md);
			// Create and send INVITE Request
			callIdHeader = sipProvider.getNewCallId();
			cSeqHeader = headerFactory.createCSeqHeader(initial, Request.INVITE);
			maxForwardsHeader = headerFactory.createMaxForwardsHeader(5);
			invite = messageFactory.createRequest(requestURI, Request.INVITE, callIdHeader, cSeqHeader, fromHeader, toHeader, viaHeaders, maxForwardsHeader, contentTypeHeader, sdp);
			
			invite.addLast((Header)contact);
			sipProvider.sendRequest(invite);
		
		} catch(InvalidArgumentException e){
			//Arguments not valid
			System.err.println(e.getMessage());
			System.exit(-1);
		} catch(ParseException e) {
			// Implementation was unable to parse a value
			System.err.println(e.getMessage() + "[" + e.getCause() + "]");
			System.exit(-1);
		} catch(SipException e) {
			// Another exception occurred
			System.err.println(e.getMessage());
			System.exit(-1);
		} catch(SdpException e){
			System.err.println(e.getMessage());
			System.exit(-1);
		}
	}
	
	public void processDialogTerminated(DialogTerminatedEvent dt) {
		// TODO Auto-generated method stub
		
	}


	
	public void processIOException(IOExceptionEvent ioee) {
		// TODO Auto-generated method stub
		
	}


	public void processRequest(RequestEvent requestReceivedEvent) {
		processSipMessage(requestReceivedEvent, true);
	}

	
	public void processResponse(ResponseEvent re) {
		processSipMessage(re, false);
		
	}

	public synchronized void processSipMessage(EventObject evt, boolean isRequest) {
		
		try {
			// If the SIP message is a Request
			if (isRequest) {
				RequestEvent requestEvent = (RequestEvent) evt;
				ServerTransaction serverTransaction;
				
				if ( requestEvent.getRequest().getMethod().compareToIgnoreCase(Request.INVITE) == 0)  
						serverTransaction = sipProvider.getNewServerTransaction(requestEvent.getRequest());
				
				else serverTransaction = requestEvent.getServerTransaction();
				
				System.out.println("SOLICITUD::::::::::: " + requestEvent.getRequest().getMethod());
				processRequest(requestEvent.getRequest(), requestEvent.getDialog(), serverTransaction);	
			}
			
			// If the SIP message is a response
			else {
				ResponseEvent responseEvent = (ResponseEvent) evt;
				processResponse(responseEvent.getResponse(), responseEvent.getDialog());				
			}
		
		} 
		catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public void processRequest(Request request, Dialog dialog, ServerTransaction serverTransaction) throws Exception {
		
		// Checks wether the request is INVITE
		if (request.getMethod().compareToIgnoreCase(Request.INVITE) == 0)
			processINVITE(request, serverTransaction);
		/**
		// Checks wether the request is PRACK
		else if (request.getMethod().compareToIgnoreCase(Request.PRACK) == 0)
			processPrack(request, serverTransaction, dialog);
		
		else if (request.getMethod().compareToIgnoreCase(Request.UPDATE) == 0)
			processUpdate(request, serverTransaction, dialog);
		*/
		else if (request.getMethod().compareToIgnoreCase(Request.ACK) == 0) {
			//processACK(request, dialog);
			ClientTransaction clientTransaction = sipProvider.getNewClientTransaction(dialog.createRequest(Request.BYE));
			dialog.sendRequest(clientTransaction);
			System.out.println("ACK received");
		}
		/*
		else if (request.getMethod().compareToIgnoreCase(Request.BYE) == 0) {
			processBYE(request, serverTransaction, dialog);
		}
		//
		else return;*/	
	}
	
	private void processINVITE(Request request, ServerTransaction serverTransaction) {
		System.out.println("Received INVITE request");	

		try{
			String aux = new String(request.getRawContent());
			SessionDescription sessionDescription = SdpFactory.getInstance().createSessionDescription(aux);
			String mediaName = null;
			MediaDescription md2 = null;
			//Is it a invite from a Clien or a Media Server
			Vector<MediaDescription> mds = sessionDescription.getMediaDescriptions(false);
			for (Iterator<MediaDescription> i1 = mds.iterator(); i1.hasNext();) {
				MediaDescription md = (MediaDescription) i1.next();
				if (md != null){
					md2 = md;
					Vector <Attribute> attrs = md.getAttributes(false);
					for (Iterator <Attribute> i2 = attrs.iterator(); i2.hasNext();) {
						Attribute attribute = (Attribute) i2.next();
						if (attribute.getName().compareTo("bc_service") == 0)
							mediaName = attribute.getValue().toString();
					}
				}
			}
			
			FromHeader fh = (FromHeader)request.getHeader("from");
			if (_clientManager.getUser(fh.getAddress().toString()) == null){
				forwardMediaRequest(request,mediaName,md2);
				_clientManager.addUser(fh.getAddress().toString(),fh.getAddress(), mediaName);
			} else 
				if (!_clientManager.getUser(fh.getAddress().toString()).isReceiving(mediaName)){
					forwardMediaRequest(request,mediaName, md2);
				}
			Response response = messageFactory.createResponse(Response.OK, request);
			ToHeader toHeader = (ToHeader)response.getHeader(ToHeader.NAME);
			toHeader.setTag("2"); //Identifier, specific to your application
			response.setHeader(headerFactory.createContactHeader(addressFactory.createAddress(_config.get_Name() + "@" + _config.get_address() + ":" + _config.get_port())));
			serverTransaction.sendResponse(response);
			
		
		} catch (SdpException e) {
			e.printStackTrace();
		} catch (SipException e) {
			e.printStackTrace();
		} catch (ParseException e) {
			e.printStackTrace();
		} catch (InvalidArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	private void forwardMediaRequest(Request request,String media, MediaDescription md) {
		Request forwardRequest = (Request)request.clone();
		try {
			forwardRequest.setRequestURI(addressFactory.createSipURI( "ASUnivTube",_config.get_address() +":"+ _config.get_port()));
			String s = forwardRequest.getHeader("max-forwards").toString();
			String s2 =s.substring(s.indexOf(":")+1).trim();
			int imf = Integer.parseInt(s2);
			imf--;
			if (s != null)
				forwardRequest.setHeader(headerFactory.createHeader("max-forwards",Integer.toString(imf)));
			else
				forwardRequest.setHeader(headerFactory.createHeader("max-forwards",Integer.toString(70)));
			StreamerInfo si =_clientManager.getForwardingEntity(media);
			//Address a = addressFactory.createAddress(s2);
			sendRequest(si,media,md);
			/*
			for (Iterator it = forwardRequest.getHeaders("to"); it.hasNext();) {
					forwardRequest.removeHeader(((Header)it.next()).getName());
			}
			forwardRequest.setHeader(headerFactory.createToHeader(a,Long.toHexString(random.nextLong())));
			forwardRequest.setHeader(headerFactory.createViaHeader(_config.get_address(), _config.get_port(), transport,"z9hG4bK" + Integer.toString(random.nextInt())));
			sipProvider.sendRequest(forwardRequest);*/
			
		} catch (ParseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} /*catch (InvalidArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SipException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}*/
		
		
	}

	private SipURI generateSessionURI(Request request) throws Exception {
		long number = random.nextLong();
		SipURI sipURI = addressFactory.createSipURI( Long.toHexString(number), _config.get_address() + ":" + _config.get_port() );
		return sipURI;
	}

	public void processResponse(Response response, Dialog dialog) throws Exception {
		// Checjs ethere the response is a Session in Progress
		/*if (response.getStatusCode() == Response.SESSION_PROGRESS)
			processSessionInProgress(response, dialog);
		
		else */if (response.getStatusCode() == Response.OK){
			/*Request ack =  dialog.createAck(1);
			dialog.sendAck( ack );*/
		}
			//processOK(response, dialog);
		/*
		else if (response.getStatusCode() == Response.RINGING)
			processRinging(response, dialog);
		
		else*/
			return;
	}

	
	public void processTimeout(TimeoutEvent te) {
		// TODO Auto-generated method stub
		
	}


	
	public void processTransactionTerminated(TransactionTerminatedEvent tte) {
		// TODO Auto-generated method stub
		
	}
	
	public void finalize() throws Exception{
		System.out.println("Deleting listening point...");
		sipStack.deleteListeningPoint(udp);
		System.out.println("ok");
	}
}
